/****************************************************************************\ 
*                                                                            *
*  SNEE (Sensor NEtwork Engine)                                              *
*  http://code.google.com/p/snee                                             *
*  Release 1.0, 24 May 2009, under New BSD License.                          *
*                                                                            *
*  Copyright (c) 2009, University of Manchester                              *
*  All rights reserved.                                                      *
*                                                                            *
*  Redistribution and use in source and binary forms, with or without        *
*  modification, are permitted provided that the following conditions are    *
*  met: Redistributions of source code must retain the above copyright       *
*  notice, this list of conditions and the following disclaimer.             *
*  Redistributions in binary form must reproduce the above copyright notice, *
*  this list of conditions and the following disclaimer in the documentation *
*  and/or other materials provided with the distribution.                    *
*  Neither the name of the University of Manchester nor the names of its     *
*  contributors may be used to endorse or promote products derived from this *
*  software without specific prior written permission.                       *
*                                                                            *
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS   *
*  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, *
*  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR    *
*  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR          *
*  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,     *
*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,       *
*  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR        *
*  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF    *
*  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING      *
*  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS        *
*  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.              *
*                                                                            *
\****************************************************************************/
package uk.ac.manchester.cs.diasmc.common.options;

/* The code in this class is taken from http://www.javaworld.com/javaworld/jw-08-2004/jw-0816-command.html?page=1,
 * and was written by Dr Matthias Laux, JavaWorld.com, 08/16/04
 * 
 * The copyright statement at http://www.javaworld.com/javaworld/common/jw-copyright.html 
 * states that "Content derived from JavaWorld may be reproduced in print or displayed online and 
 * distributed, for free, in limited quantities for nonprofit, educational purposes with proper attribution." 
 */

/**
 * The central class for option processing. Sets are identified by their name, but there is also
 * an anonymous default set, which is very convenient if an application requieres only one set.
 */

public class Options {

    private final static String CLASS = "Options";

    /**
     * The name used internally for the default set
     */

    public final static String DEFAULT_SET = "DEFAULT_OPTION_SET";

    /**
     * An enum encapsulating the possible separators between value options and their actual values.
     */

    public enum Separator {

	/**
	 * Separate option and value by ":"
	 */

	COLON(':'),

	/**
	 * Separate option and value by "="
	 */

	EQUALS('='),

	/**
	 * Separate option and value by blank space
	 */

	BLANK(' '), // Or, more precisely, whitespace (as allowed by the CLI)

	/**
	 * This is just a placeholder in case no separator is required (i. e. for non-value options)
	 */

	NONE('D'); // NONE is a placeholder in case no separator is required, 'D' is just an arbitrary dummy value

	private char c;

	private Separator(char c) {
	    this.c = c;
	}

	/**
	 * Return the actual separator character
	 * <p>
	 * @return The actual separator character
	 */

	char getName() {
	    return c;
	}

    }

    /**
     * An enum encapsulating the possible prefixes identifying options (and separating them from command line data items)
     */

    public enum Prefix {

	/**
	 * Options start with a "-" (typically on Unix platforms)
	 */

	DASH('-'),

	/**
	 * Options start with a "/" (typically on Windows platforms)
	 */

	SLASH('/');

	private char c;

	private Prefix(char c) {
	    this.c = c;
	}

	/**
	 * Return the actual prefix character
	 * <p>
	 * @return The actual prefix character
	 */

	char getName() {
	    return c;
	}

    }

    /**
     * An enum encapsulating the possible multiplicities for options
     */

    public enum Multiplicity {

	/**
	 * Option needs to occur exactly once
	 */

	ONCE,

	/**
	 * Option needs to occur at least once
	 */

	ONCE_OR_MORE,

	/**
	 * Option needs to occur either once or not at all
	 */

	ZERO_OR_ONE,

	/**
	 * Option can occur any number of times
	 */

	ZERO_OR_MORE;

    }

    private java.util.HashMap<String, OptionSet> optionSets = new java.util.HashMap<String, OptionSet>();

    private Prefix prefix = null;

    private Multiplicity defaultMultiplicity = null;

    private String[] arguments = null;

    private boolean ignoreUnmatched = false;

    private int defaultMinData = 0;

    private int defaultMaxData = 0;

    private StringBuffer checkErrors = null;

    /**
     * Constructor
     * <p>
     * @param args                The command line arguments to check
     * @param prefix              The prefix to use for all command line options. It can only be set here for all options at
     *                            the same time
     * @param defaultMultiplicity The default multiplicity to use for all options (can be overridden when adding an option)
     * @param defMinData          The default minimum number of data items for all sets (can be overridden when adding a set)
     * @param defMaxData          The default maximum number of data items for all sets (can be overridden when adding a set)
     * <p>
     * @throws IllegalArgumentException If either <code>args</code>, <code>prefix</code>, or <code>defaultMultiplicity</code>
     *                                  is <code>null</code> - or if the data range values don't make sense
     */

    public Options(String args[], Prefix prefix,
	    Multiplicity defaultMultiplicity, int defMinData, int defMaxData) {

	if (args == null)
	    throw new IllegalArgumentException(CLASS + ": args may not be null");
	if (prefix == null)
	    throw new IllegalArgumentException(CLASS
		    + ": prefix may not be null");
	if (defaultMultiplicity == null)
	    throw new IllegalArgumentException(CLASS
		    + ": defaultMultiplicity may not be null");

	if (defMinData < 0)
	    throw new IllegalArgumentException(CLASS
		    + ": defMinData must be >= 0");
	if (defMaxData < defMinData)
	    throw new IllegalArgumentException(CLASS
		    + ": defMaxData must be >= defMinData");

	arguments = new String[args.length];
	int i = 0;
	for (String s : args)
	    arguments[i++] = s;

	this.prefix = prefix;
	this.defaultMultiplicity = defaultMultiplicity;
	this.defaultMinData = defMinData;
	this.defaultMaxData = defMaxData;

    }

    /**
     * Constructor
     * <p>
     * @param args                The command line arguments to check
     * @param prefix              The prefix to use for all command line options. It can only be set here for all options at
     *                            the same time
     * @param defaultMultiplicity The default multiplicity to use for all options (can be overridden when adding an option)
     * @param data                The default minimum and maximum number of data items for all sets (can be overridden when adding a set)
     * <p>
     * @throws IllegalArgumentException If either <code>args</code>, <code>prefix</code>, or <code>defaultMultiplicity</code>
     *                                  is <code>null</code> - or if the data range value doesn't make sense
     */

    public Options(String args[], Prefix prefix,
	    Multiplicity defaultMultiplicity, int data) {
	this(args, prefix, defaultMultiplicity, data, data);
    }

    /**
     * Constructor. The default number of data items is set to 0.
     * <p>
     * @param args                The command line arguments to check
     * @param prefix              The prefix to use for all command line options. It can only be set here for all options at
     *                            the same time
     * @param defaultMultiplicity The default multiplicity to use for all options (can be overridden when adding an option)
     * <p>
     * @throws IllegalArgumentException If either <code>args</code>, <code>prefix</code>, or <code>defaultMultiplicity</code>
     *                                  is <code>null</code>
     */

    public Options(String args[], Prefix prefix,
	    Multiplicity defaultMultiplicity) {
	this(args, prefix, defaultMultiplicity, 0, 0);
    }

    /**
     * Constructor. The prefix is set to {@link Prefix#DASH}.
     * <p>
     * @param args                The command line arguments to check
     * @param defaultMultiplicity The default multiplicity to use for all options (can be overridden when adding an option)
     * @param defMinData          The default minimum number of data items for all sets (can be overridden when adding a set)
     * @param defMaxData          The default maximum number of data items for all sets (can be overridden when adding a set)
     * <p>
     * @throws IllegalArgumentException If either <code>args</code> or <code>defaultMultiplicity</code>
     *                                  is <code>null</code> - or if the data range values don't make sense
     */

    public Options(String args[], Multiplicity defaultMultiplicity,
	    int defMinData, int defMaxData) {
	this(args, Prefix.DASH, defaultMultiplicity, defMinData, defMaxData);
    }

    /**
     * Constructor. The prefix is set to {@link Prefix#DASH}.
     * <p>
     * @param args                The command line arguments to check
     * @param defaultMultiplicity The default multiplicity to use for all options (can be overridden when adding an option)
     * @param data                The default minimum and maximum number of data items for all sets (can be overridden when adding a set)
     * <p>
     * @throws IllegalArgumentException If either <code>args</code> or <code>defaultMultiplicity</code>
     *                                  is <code>null</code> - or if the data range value doesn't make sense
     */

    public Options(String args[], Multiplicity defaultMultiplicity, int data) {
	this(args, Prefix.DASH, defaultMultiplicity, data, data);
    }

    /**
     * Constructor. The prefix is set to {@link Prefix#DASH}, and the default number of data items is set to 0.
     * <p>
     * @param args                The command line arguments to check
     * @param defaultMultiplicity The default multiplicity to use for all options (can be overridden when adding an option)
     * <p>
     * @throws IllegalArgumentException If either <code>args</code> or <code>defaultMultiplicity</code>
     *                                  is <code>null</code>
     */

    public Options(String args[], Multiplicity defaultMultiplicity) {
	this(args, Prefix.DASH, defaultMultiplicity, 0, 0);
    }

    /**
     * Constructor. The prefix is set to {@link Prefix#DASH}, the default number of data items is set to 0, and
     * the multiplicity is set to {@link Multiplicity#ONCE}.
     * <p>
     * @param args The command line arguments to check
     * <p>
     * @throws IllegalArgumentException If <code>args</code> is <code>null</code>
     */

    public Options(String args[]) {
	this(args, Prefix.DASH, Multiplicity.ONCE);
    }

    /**
     * Constructor. The prefix is set to {@link Prefix#DASH}, and
     * the multiplicity is set to {@link Multiplicity#ONCE}.
     * <p>
     * @param args The command line arguments to check
     * @param data The default minimum and maximum number of data items for all sets (can be overridden when adding a set)
     * <p>
     * @throws IllegalArgumentException If <code>args</code> is <code>null</code> - or if the data range value doesn't make sense
     */

    public Options(String args[], int data) {
	this(args, Prefix.DASH, Multiplicity.ONCE, data, data);
    }

    /**
     * Constructor. The prefix is set to {@link Prefix#DASH}, and
     * the multiplicity is set to {@link Multiplicity#ONCE}.
     * <p>
     * @param args       The command line arguments to check
     * @param defMinData The default minimum number of data items for all sets (can be overridden when adding a set)
     * @param defMaxData The default maximum number of data items for all sets (can be overridden when adding a set)
     * <p>
     * @throws IllegalArgumentException If <code>args</code> is <code>null</code> - or if the data range values don't make sense
     */

    public Options(String args[], int defMinData, int defMaxData) {
	this(args, Prefix.DASH, Multiplicity.ONCE, defMinData, defMaxData);
    }

    /**
     * Constructor. The default number of data items is set to 0, and
     * the multiplicity is set to {@link Multiplicity#ONCE}.
     * <p>
     * @param args   The command line arguments to check
     * @param prefix The prefix to use for all command line options. It can only be set here for all options at
     *               the same time
     * <p>
     * @throws IllegalArgumentException If either <code>args</code> or <code>prefix</code> is <code>null</code>
     */

    public Options(String args[], Prefix prefix) {
	this(args, prefix, Multiplicity.ONCE, 0, 0);
    }

    /**
     * Constructor. The multiplicity is set to {@link Multiplicity#ONCE}.
     * <p>
     * @param args   The command line arguments to check
     * @param prefix The prefix to use for all command line options. It can only be set here for all options at
     * @param data   The default minimum and maximum number of data items for all sets (can be overridden when adding a set)
     * <p>
     * @throws IllegalArgumentException If either <code>args</code> or <code>prefix</code> is <code>null</code>
     *                                  - or if the data range value doesn't make sense
     */

    public Options(String args[], Prefix prefix, int data) {
	this(args, prefix, Multiplicity.ONCE, data, data);
    }

    /**
     * Constructor. The multiplicity is set to {@link Multiplicity#ONCE}.
     * <p>
     * @param args       The command line arguments to check
     * @param prefix     The prefix to use for all command line options. It can only be set here for all options at
     *                   the same time
     * @param defMinData The default minimum number of data items for all sets (can be overridden when adding a set)
     * @param defMaxData The default maximum number of data items for all sets (can be overridden when adding a set)
     * <p>
     * @throws IllegalArgumentException If either <code>args</code> or <code>prefix</code> is <code>null</code>
     *                                  - or if the data range values don't make sense
     */

    public Options(String args[], Prefix prefix, int defMinData, int defMaxData) {
	this(args, prefix, Multiplicity.ONCE, defMinData, defMaxData);
    }

    /**
     * Return the (first) matching set. This invocation does not ignore unmatched options and requires that
     * data items are the last ones on the command line.
     * <p>
     * @return The first set which matches (i. e. the <code>check()</code> method returns <code>true</code>) - or
     *         <code>null</code>, if no set matches.
     */

    public OptionSet getMatchingSet() {
	return getMatchingSet(false, true);
    }

    /**
     * Return the (first) matching set.
     * <p>
     * @param ignoreUnmatched A boolean to select whether unmatched options can be ignored in the checks or not
     * @param requireDataLast A boolean to indicate whether the data items have to be the last ones on the command line or not
     * <p>
     * @return The first set which matches (i. e. the <code>check()</code> method returns <code>true</code>) - or
     *         <code>null</code>, if no set matches.
     */

    public OptionSet getMatchingSet(boolean ignoreUnmatched,
	    boolean requireDataLast) {
	for (String setName : optionSets.keySet())
	    if (check(setName, ignoreUnmatched, requireDataLast))
		return optionSets.get(setName);
	return null;
    }

    /**
     * Add an option set.
     * <p>
     * @param setName The name for the set. This must be a unique identifier
     * @param minData The minimum number of data items for this set
     * @param maxData The maximum number of data items for this set
     * <p>
     * @return The new <code>Optionset</code> instance created. This is useful to allow chaining of <code>addOption()</code>
     *         calls right after this method
     */

    public OptionSet addSet(String setName, int minData, int maxData) {
	if (setName == null)
	    throw new IllegalArgumentException(CLASS
		    + ": setName may not be null");
	if (optionSets.containsKey(setName))
	    throw new IllegalArgumentException(CLASS + ": a set with the name "
		    + setName + " has already been defined");
	OptionSet os = new OptionSet(prefix, defaultMultiplicity, setName,
		minData, maxData);
	optionSets.put(setName, os);
	return os;
    }

    /**
     * Add an option set.
     * <p>
     * @param setName The name for the set. This must be a unique identifier
     * @param data    The minimum and maximum number of data items for this set
     * <p>
     * @return The new <code>Optionset</code> instance created. This is useful to allow chaining of <code>addOption()</code>
     *         calls right after this method
     */

    public OptionSet addSet(String setName, int data) {
	return addSet(setName, data, data);
    }

    /**
     * Add an option set. The defaults for the number of data items are used.
     * <p>
     * @param setName The name for the set. This must be a unique identifier
     * <p>
     * @return The new <code>Optionset</code> instance created. This is useful to allow chaining of <code>addOption()</code>
     *         calls right after this method
     */

    public OptionSet addSet(String setName) {
	return addSet(setName, defaultMinData, defaultMaxData);
    }

    /**
     * Return an option set - or <code>null</code>, if no set with the given name exists
     * <p>
     * @param setName The name for the set to retrieve
     * <p>
     * @return The set to retrieve (or <code>null</code>, if no set with the given name exists)
     */

    public OptionSet getSet(String setName) {
	return optionSets.get(setName);
    }

    /**
     * This returns the (anonymous) default set
     * <p>
     * @return The default set
     */

    public OptionSet getSet() {
	if (getSet(DEFAULT_SET) == null)
	    addSet(DEFAULT_SET, defaultMinData, defaultMaxData);
	return getSet(DEFAULT_SET);
    }

    /**
     * The error messages collected during the last option check (invocation of any of the <code>check()</code> methods). This
     * is useful to determine what was wrong with the command line arguments provided
     * <p>
     * @return A string with all collected error messages
     */

    public String getCheckErrors() {
	return checkErrors.toString();
    }

    /**
     * Run the checks for the default set. <code>ignoreUnmatched</code> is set to <code>false</code>, and
     * <code>requireDataLast</code> is set to <code>true</code>.
     * <p>
     * @return A boolean indicating whether all checks were successful or not
     */

    public boolean check() {
	return check(DEFAULT_SET, false, true);
    }

    /**
     * Run the checks for the default set.
     * <p>
     * @param ignoreUnmatched A boolean to select whether unmatched options can be ignored in the checks or not
     * @param requireDataLast A boolean to indicate whether the data items have to be the last ones on the command line or not
     * <p>
     * @return A boolean indicating whether all checks were successful or not
     */

    public boolean check(boolean ignoreUnmatched, boolean requireDataLast) {
	return check(DEFAULT_SET, ignoreUnmatched, requireDataLast);
    }

    /**
     * Run the checks for the given set. <code>ignoreUnmatched</code> is set to <code>false</code>, and
     * <code>requireDataLast</code> is set to <code>true</code>.
     * <p>
     * @param setName The name for the set to check
     * <p>
     * @return A boolean indicating whether all checks were successful or not
     * <p>
     * @throws IllegalArgumentException If either <code>setName</code> is <code>null</code>, or the set is unknown.
     */

    public boolean check(String setName) {
	return check(setName, false, true);
    }

    /**
     * Run the checks for the given set.
     * <p>
     * @param setName The name for the set to check
     * @param ignoreUnmatched A boolean to select whether unmatched options can be ignored in the checks or not
     * @param requireDataLast A boolean to indicate whether the data items have to be the last ones on the command line or not
     * <p>
     * @return A boolean indicating whether all checks were successful or not
     * <p>
     * @throws IllegalArgumentException If either <code>setName</code> is <code>null</code>, or the set is unknown.
     */

    public boolean check(String setName, boolean ignoreUnmatched,
	    boolean requireDataLast) {

	if (setName == null)
	    throw new IllegalArgumentException(CLASS
		    + ": setName may not be null");
	if (optionSets.get(setName) == null)
	    throw new IllegalArgumentException(CLASS + ": Unknown OptionSet: "
		    + setName);

	//IG: hack
	if (arguments.length == 0) {
	    return true;
	}

	checkErrors = new StringBuffer();
	checkErrors.append("Checking set ");
	checkErrors.append(setName);
	checkErrors.append('\n');

	//.... Access the data for the set to use

	OptionSet set = optionSets.get(setName);
	java.util.ArrayList<OptionData> options = set.getOptionData();
	java.util.ArrayList<String> data = set.getData();
	java.util.ArrayList<String> unmatched = set.getUnmatched();

	//.... Catch some trivial cases

	if (options.size() == 0) { // No options have been defined at all
	    if (arguments.length == 0) { // No arguments have been given: in this case, this is a success
		return true;
	    } else {
		checkErrors
			.append("No options have been defined, nothing to check\n");
		return false;
	    }
	} else if (arguments.length == 0) { // Options have been defined, but no arguments given
	    checkErrors
		    .append("Options have been defined, but no arguments have been given; nothing to check\n");
	    return false;
	}

	//.... Parse all the arguments given

	int ipos = 0;
	int offset = 0;
	java.util.regex.Matcher m = null;
	String value = null;
	String detail = null;
	String next = null;
	String key = null;
	String pre = Character.toString(prefix.getName());
	boolean add = true;
	boolean[] matched = new boolean[arguments.length];

	for (int i = 0; i < matched.length; i++)
	    // Initially, we assume there was no match at all
	    matched[i] = false;

	while (true) {

	    value = null;
	    detail = null;
	    offset = 0;
	    add = true;
	    key = arguments[ipos];

	    for (OptionData optionData : options) { // For each argument, we may need to check all defined options
		m = optionData.getPattern().matcher(key);
		if (m.lookingAt()) {
		    if (optionData.useValue()) { // The code section for value options
			if (optionData.useDetail()) {
			    detail = m.group(1);
			    offset = 2; // required for correct Matcher.group access below
			}
			if (optionData.getSeparator() == Separator.BLANK) { // In this case, the next argument must be the value
			    if (ipos + 1 == arguments.length) { // The last argument, thus no value follows it: Error
				checkErrors
					.append("At end of arguments - no value found following argument ");
				checkErrors.append(key);
				checkErrors.append('\n');
				add = false;
			    } else {
				next = arguments[ipos + 1];
				if (next.startsWith(pre)) { // The next one is an argument, not a value: Error
				    checkErrors
					    .append("No value found following argument ");
				    checkErrors.append(key);
				    checkErrors.append('\n');
				    add = false;
				} else {
				    value = next;
				    matched[ipos++] = true; // Mark the key and the value
				    matched[ipos] = true;
				}
			    }
			} else { // The value follows the separator in this case
			    value = m.group(1 + offset);
			    matched[ipos] = true;
			}
		    } else { // Simple, non-value options
			matched[ipos] = true;
		    }

		    if (add)
			optionData.addResult(value, detail); // Store the result
		    break; // No need to check more options, we have a match
		}
	    }

	    ipos++; // Advance to the next argument to check
	    if (ipos >= arguments.length)
		break; // Terminating condition for the check loop

	}

	//.... Identify unmatched arguments and actual (non-option) data

	int first = -1; // Required later for requireDataLast
	for (int i = 0; i < matched.length; i++) { // Assemble the list of unmatched options
	    if (!matched[i]) {
		if (arguments[i].startsWith(pre)) { // This is an unmatched option
		    unmatched.add(arguments[i]);
		    checkErrors
			    .append("No matching option found for argument ");
		    checkErrors.append(arguments[i]);
		    checkErrors.append('\n');
		} else { // This is actual data
		    if (first < 0)
			first = i;
		    data.add(arguments[i]);
		}
	    }
	}

	//.... Checks to determine overall success; start with multiplicity of options

	boolean err = true;

	for (OptionData optionData : options) {

	    key = optionData.getKey();
	    err = false; // Local check result for one option

	    switch (optionData.getMultiplicity()) {
	    case ONCE:
		if (optionData.getResultCount() != 1)
		    err = true;
		break;
	    case ONCE_OR_MORE:
		if (optionData.getResultCount() == 0)
		    err = true;
		break;
	    case ZERO_OR_ONE:
		if (optionData.getResultCount() > 1)
		    err = true;
		break;
	    }

	    if (err) {
		checkErrors
			.append("Wrong number of occurences found for argument ");
		checkErrors.append(prefix.getName());
		checkErrors.append(key);
		checkErrors.append('\n');
		return false;
	    }

	}

	//.... Check range for data

	if (data.size() < set.getMinData() || data.size() > set.getMaxData()) {
	    checkErrors.append("Invalid number of data arguments: ");
	    checkErrors.append(data.size());
	    checkErrors.append(" (allowed range: ");
	    checkErrors.append(set.getMinData());
	    checkErrors.append(" ... ");
	    checkErrors.append(set.getMaxData());
	    checkErrors.append(")\n");
	    return false;
	}

	//.... Check for location of the data in the list of command line arguments

	if (requireDataLast && data.size() > 0) { //IG: bug fix
	    if (first + data.size() != arguments.length) {
		checkErrors
			.append("Invalid data specification: data arguments are not the last ones on the command line\n");
		return false;
	    }
	}

	//.... Check for unmatched arguments

	if (!ignoreUnmatched && unmatched.size() > 0)
	    return false; // Don't accept unmatched arguments

	//.... If we made it to here, all checks were successful

	return true;

    }

    /**
     * Add the given non-value option to <i>all</i> known sets.
     * See {@link OptionSet#addOption(String)} for details.
     */

    public void addOptionAllSets(String key) {
	for (String setName : optionSets.keySet())
	    optionSets.get(setName).addOption(key, defaultMultiplicity);
    }

    /**
     * Add the given non-value option to <i>all</i> known sets.
     * See {@link OptionSet#addOption(String, Options.Multiplicity)} for details.
     */

    public void addOptionAllSets(String key, Multiplicity multiplicity) {
	for (String setName : optionSets.keySet())
	    optionSets.get(setName).addOption(key, false, Separator.NONE,
		    false, multiplicity);
    }

    /**
     * Add the given value option to <i>all</i> known sets.
     * See {@link OptionSet#addOption(String, Options.Separator)} for details.
     */

    public void addOptionAllSets(String key, Separator separator) {
	for (String setName : optionSets.keySet())
	    optionSets.get(setName).addOption(key, false, separator, true,
		    defaultMultiplicity);
    }

    /**
     * Add the given value option to <i>all</i> known sets.
     * See {@link OptionSet#addOption(String, Options.Separator, Options.Multiplicity)} for details.
     */

    public void addOptionAllSets(String key, Separator separator,
	    Multiplicity multiplicity) {
	for (String setName : optionSets.keySet())
	    optionSets.get(setName).addOption(key, false, separator, true,
		    multiplicity);
    }

    /**
     * Add the given value option to <i>all</i> known sets.
     * See {@link OptionSet#addOption(String, boolean, Options.Separator)} for details.
     */

    public void addOptionAllSets(String key, boolean details,
	    Separator separator) {
	for (String setName : optionSets.keySet())
	    optionSets.get(setName).addOption(key, details, separator, true,
		    defaultMultiplicity);
    }

    /**
     * Add the given value option to <i>all</i> known sets.
     * See {@link OptionSet#addOption(String, boolean, Options.Separator, Options.Multiplicity)} for details.
     */

    public void addOptionAllSets(String key, boolean details,
	    Separator separator, Multiplicity multiplicity) {
	for (String setName : optionSets.keySet())
	    optionSets.get(setName).addOption(key, details, separator, true,
		    multiplicity);
    }

    /**
     * This is the overloaded {@link Object#toString()} method, and it is provided mainly for debugging
     * purposes.
     * <p>
     * @return A string representing the instance
     */

    public String toString() {

	StringBuffer sb = new StringBuffer();

	for (OptionSet set : optionSets.values()) {
	    sb.append("Set: ");
	    sb.append(set.getSetName());
	    sb.append('\n');
	    for (OptionData data : set.getOptionData()) {
		sb.append(data.toString());
		sb.append('\n');
	    }
	}

	return sb.toString();

    }

}
